{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Uploading Custom History when Creating a Custom Basket\n",
    "\n",
    "When creating a custom basket, Marquee by default will publish up to five years of pricing and composition history based\n",
    "on the composition entered during creation. If you choose to bypass this behavior by setting the `default_backcast`\n",
    "parameter to `False`, you may upload your own custom history any time after the basket has been created. This tutorial\n",
    "will show you how to submit a basket backcast, including best practices to avoid mapping errors, and more!\n",
    "\n",
    "**Note:** You must specify this setting during creation in order to perform the below steps. If you'd like to submit\n",
    "custom history for a basket that has already been created and did not originally select this option, you'll need to\n",
    "create a new basket."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 1: Authenticate & Initialize your session\n",
    "\n",
    "First you will import the necessary modules and add your client id and client secret."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import datetime as dt\n",
    "\n",
    "from gs_quant.api.gs.reports import GsReportApi\n",
    "from gs_quant.markets.baskets import Basket\n",
    "from gs_quant.markets.position_set import Currency, Position, PositionSet, PositionSetWeightingStrategy\n",
    "from gs_quant.session import Environment, GsSession\n",
    "\n",
    "client = 'CLIENT ID'\n",
    "secret = 'CLIENT SECRET'\n",
    "\n",
    "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('modify_product_data',))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 2: Extract your positions into dated position sets\n",
    "\n",
    "Next you'll need to create a PositionSet for each date your basket had position changes historically. Examples of\n",
    "position changes are anything that affect the price or composition of your basket. For example, periodic weighted\n",
    "rebalances, identifier changes or other corporate actions, adds/drops, etc.\n",
    "\n",
    "When Marquee goes to upload your history, we'll convert your position weights into quantities and carry those forward\n",
    "until the next date with position changes occurs. If you do not have position weights and only have quantities at the\n",
    "time of upload, make sure to follow step #4.\n",
    "\n",
    "For the sake of simplicity in this tutorial, we'll show a very basic example of position sets that may represent\n",
    "a basket's composition history. However based on your own personal Input/Output preferences and setup, you'll likely want to make\n",
    "some adjustments in order to aggregate your positions into unique PositionSet objects per date. See\n",
    "[position_set examples](../examples/03_basket_creation/position_set/0012_create_historical_position_sets_from_excel.ipynb)\n",
    "for alternate methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "positions = [Position(identifier='AAPL UW', weight=0.5), Position(identifier='MSFT UW', weight=0.5)]\n",
    "\n",
    "pos_set_1 = PositionSet(positions, dt.date(2021, 6, 3))\n",
    "pos_set_2 = PositionSet(positions, dt.date(2022, 1, 2))\n",
    "pos_set_3 = PositionSet(positions, dt.date(2022, 6, 1))\n",
    "pos_set_4 = PositionSet(positions, dt.date(2023, 1, 4))\n",
    "\n",
    "position_sets = [pos_set_1, pos_set_2, pos_set_3, pos_set_4]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 3: Resolve your position sets and check for unmapped assets\n",
    "\n",
    "Now that you have your position sets created, it's time to resolve your positions historically. This will help confirm\n",
    "that each asset identifier can be mapped as of the provided position date.\n",
    "\n",
    "If you face any mapping issues, double check that your identifiers are valid. Consider identifier changes (e.g.,\n",
    "FB UW -> META UW), listed status, IPOs, etc. If you believe you have corrected all assets with these issues and are still\n",
    "experiencing problems mapping your positions, you may remove these identifiers or email the\n",
    "[baskets support team](mailto:gs-marquee-baskets-support@gs.com?subject=Failed%20To%20Map%20Positions%20for%20Basket%20Backcast)\n",
    "for assistance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "for position_set in position_sets:\n",
    "    position_set.resolve()\n",
    "    if position_set.unresolved_positions is not None and len(position_set.unresolved_positions):\n",
    "        print(\n",
    "            f'Error resolving assets on {position_set.date.strftime(\"%Y-%m-%d\")}: {position_set.unresolved_positions}'\n",
    "        )\n",
    "        \"\"\" Uncomment the below to removed unresolved positions from your position set \"\"\"\n",
    "        # position_set.remove_unresolved_positions()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 4 (Optional): Convert position quantities to weights\n",
    "\n",
    "Basket backcasts require each position to have a specified weight. If you only have historical quantities, you may\n",
    "call the `price` function on each position set in order to extract these weights via Marquee. Otherwise you may skip\n",
    "this step.\n",
    "\n",
    "Similar to step #3, you can double check if any assets are unable to be priced and either remove these from your\n",
    "position set or contact the [baskets support team](mailto:gs-marquee-baskets-support@gs.com?subject=Failed%20To%20Price%20Positions%20for%20Basket%20Backcast)\n",
    "for help."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "currency = Currency.USD  # replace with desired currency\n",
    "\n",
    "for position_set in position_sets:\n",
    "    position_set.price(\n",
    "        currency=currency, use_unadjusted_close_price=False, weighting_strategy=PositionSetWeightingStrategy.Quantity\n",
    "    )\n",
    "    if position_set.unpriced_positions is not None and len(position_set.unpriced_positions):\n",
    "        print(f'Error pricing assets on {position_set.date.strftime(\"%Y-%m-%d\")}: {position_set.unpriced_positions}')\n",
    "        \"\"\" Uncomment the below to removed unpriced positions from your position set \"\"\"\n",
    "        # position_set.remove_unpriced_positions()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### Quick Tip!\n",
    "If you had to remove positions from any position set for one of the reasons described above, the total weight of\n",
    "each position might no longer add up to 1. If this is the case, you can call `redistribute_weights` on any misweighted\n",
    "position sets, which will redistribute the remaining weights proportionally among each position."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "for position_set in position_sets:\n",
    "    position_set.redistribute_weights()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 5: Fetch your basket and upload your history\n",
    "\n",
    "Once your positions have all been mapped and assigned weights, you're ready to submit to Marquee! Fetch your basket\n",
    "and call `upload_position_history` using the position sets from above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "basket = Basket.get('GSMBXXXX')\n",
    "basket.upload_position_history(position_sets)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 6 (Optional): Reschedule or Cancel failed report jobs\n",
    "\n",
    "There are several cases that can cause a backcast report to fail, including long history length, large number of\n",
    "positions/rebalance frequency, historical errors for assets in Marquee, and more. The above script will catch as\n",
    "many issues as possible prior to upload but there are sometimes still failures that require investigation from the\n",
    "support team to fix.\n",
    "\n",
    "If you find that your backcasts are failing, you can reschedule any failed report jobs using the below snippet to catch\n",
    "for intermittent errors such as timeouts or memory issues on Marquee side. If the errors persist, you may reach out\n",
    "to the [baskets support team](mailto:gs-marquee-baskets-support@gs.com) for further information.\n",
    "\n",
    "If your backcast reports are failing due a non-Marquee issue and you need to re-upload your position history, you'll\n",
    "need to cancel any failed report jobs in order to resubmit your adjusted positions. Then you may repeat steps #1-5."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "backcast_reports = GsReportApi.get_reports(position_source_id=basket.get_marquee_id(), report_type='Basket Backcast')\n",
    "report_ids, report_job_ids = [r.id for r in backcast_reports], []\n",
    "for report_id in report_ids:\n",
    "    report_jobs = GsReportApi.get_report_jobs(report_id)\n",
    "    report_job_ids = [rj.get('id') for rj in report_jobs if rj.get('status') == 'error']\n",
    "\n",
    "\"\"\" Use this to RESCHEDULE failed report jobs \"\"\"\n",
    "# for report_job in report_job_ids:\n",
    "#     print(f'Rescheduling report job {report_job.get(\"id\")}')\n",
    "#     GsReportApi.reschedule_report_job(report_job.get('id'))\n",
    "\n",
    "\"\"\" Use this to CANCEL failed report jobs \"\"\"\n",
    "# for report_job in report_job_ids:\n",
    "#     print(f'Cancelling report job {report_job.get(\"id\")}')\n",
    "#     GsReportApi.cancel_report_job(report_job.get('id'))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
